/*
Copyright 2024 The Kruise Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package writer

import (
	"bytes"
	"context"
	"errors"
	"fmt"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	clientset "k8s.io/client-go/kubernetes"
	"k8s.io/klog/v2"

	"github.com/openkruise/kruise/pkg/webhook/util/generator"
)

const (
	mutatingWebhookConfigurationName = "kruise-mutating-webhook-configuration"
)

var currentExternalCerts *generator.Artifacts

// externalCertWriter provisions the certificate by reading from the k8s mutating webhook configuration.
type externalCertWriter struct {
	*ExternalCertWriterOptions
}

// ExternalCertWriterOptions is options for constructing a externalCertWriter.
type ExternalCertWriterOptions struct {
	Clientset clientset.Interface
}

var _ CertWriter = &externalCertWriter{}

func (ops *ExternalCertWriterOptions) validate() error {
	if ops.Clientset == nil {
		return errors.New("client must be set in externalCertWriterOptions")
	}
	return nil
}

func NewExternalCertWriter(ops ExternalCertWriterOptions) (CertWriter, error) {
	err := ops.validate()
	if err != nil {
		return nil, err
	}
	return &externalCertWriter{ExternalCertWriterOptions: &ops}, nil
}

func (s *externalCertWriter) EnsureCert(dnsName string) (*generator.Artifacts, bool, error) {
	// Read certs from mutating webhook configuration generated by external
	certs, err := s.read()
	if err != nil {
		return nil, false, err
	}
	// check if the certs are updated since last read
	if currentExternalCerts != nil && compareCerts(certs, currentExternalCerts) {
		klog.Info("external certs are not updated")
		return certs, false, nil
	}

	currentExternalCerts = certs
	return certs, true, nil
}

var _ certReadWriter = &externalCertWriter{}

func (s *externalCertWriter) write() (*generator.Artifacts, error) {
	return nil, nil
}

func (s *externalCertWriter) overwrite(resourceVersion string) (*generator.Artifacts, error) {
	return nil, nil
}

func (s *externalCertWriter) read() (*generator.Artifacts, error) {
	mutatingConfig, err := s.Clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(context.TODO(), mutatingWebhookConfigurationName, metav1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("not found MutatingWebhookConfiguration %s", mutatingWebhookConfigurationName)
	}
	if len(mutatingConfig.Webhooks) == 0 {
		return nil, fmt.Errorf("not found webhook in MutatingWebhookConfiguration %s", mutatingWebhookConfigurationName)
	}

	caBundle := mutatingConfig.Webhooks[0].ClientConfig.CABundle
	return &generator.Artifacts{CACert: caBundle}, nil
}

func compareCerts(certsA, certsB *generator.Artifacts) bool {
	if !bytes.Equal(certsA.CACert, certsB.CACert) || !bytes.Equal(certsA.CAKey, certsB.CAKey) || !bytes.Equal(certsA.Cert, certsB.Cert) || !bytes.Equal(certsA.Key, certsB.Key) {
		return false
	}
	return true
}
